package cn.mesmile.shiro.config;

import cn.hutool.core.date.DateUtil;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author zb
 * @date 2021/2/18 16:14
 * @Description 自定义验证错误次数 [3] 就抛出异常 , 防止暴力破解
 */
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {

    private static Map<String,RetryLimitHashed> map = new HashMap<String,RetryLimitHashed>();

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        //1.通过令牌获取用户名
        String username = (String)token.getPrincipal();
        //2.判断缓存中是否包含含有此用户名的Element
        RetryLimitHashed retryLimitHashed = map.get(username);
        if (retryLimitHashed != null){
            AtomicInteger count = retryLimitHashed.count;
            int i = count.get();
            //如果大于2 并且在限制时间内【输错 3 次密码即不可访问】
            long current = System.currentTimeMillis();
            if (i > 2 && retryLimitHashed.expireDate!= null && retryLimitHashed.expireDate.getTime() > current ){
                throw new ExcessiveAttemptsException("重试次数过多，请在 "+ DateUtil.formatDateTime(retryLimitHashed.expireDate) + "后重试。");
            }
            if (i > 2 && retryLimitHashed.expireDate!= null && retryLimitHashed.expireDate.getTime() < current ){
                // 允许重试
                retryLimitHashed.count.set(0);
            }
        }
        //6.如果小于5 就调用父类的doCredentialsMatch方法进行密码验证
        boolean matchs = super.doCredentialsMatch(token, info);
        if (!matchs){
            if (retryLimitHashed == null){
                retryLimitHashed = new RetryLimitHashed();
                retryLimitHashed.count.incrementAndGet();
                map.put(username,retryLimitHashed);
            }else {
                int i = retryLimitHashed.count.incrementAndGet();
                if (i == 3){
                    // 两小时后重试
                    retryLimitHashed.expireDate = DateUtil.offsetHour(new Date(), 2);
                }
            }
        }else {
            if (retryLimitHashed != null){
                // 如果验证通过，就将该username从缓存中清除
                map.remove(username);
            }
        }
        return matchs;
    }

    public class RetryLimitHashed {
        // 过期时间
        Date expireDate;
        // 重试次数
        AtomicInteger count = new AtomicInteger(0);
    }

}
